#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "project.h"
#include "util.h"
#include "binarybuffer.h"
#include "getMemory.h"

#if (_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600)
#include <sys/select.h>							/* According to POSIX.1-2001 */
#else
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#endif

void * read_next_buf(void*);
unsigned int FPHash(const char*);
long check_file_in_hash_table(const char*);
void * check_buffer_file_node_invalid(void*);
void free_list(F_BUF);
void mysleep();
void show_hash_table();

char *FILEPATH;
char *PROFILEPATH;
char *BACKFILE;
u_int32_t HASH_TABLE_SIZE;                          //hash table size
u_int32_t BLOCK_SIZE;                               //文件缓存块大小
u_int32_t FILENAME_LENGTH;
u_int32_t SECTION_LENGTH;                           //每个文件节点备份字段长度
u_int32_t BUFFER_FILE_SIZE;                         //进行缓存文件大小不超过1MB
u_int32_t BUFFER_INVALID_TIME;                      //文件在内存缓存时，超过24h没被访问将失效，被从内存中移除
u_int32_t BACKUP_TIME;                              //每隔1h备份缓存
u_int64_t MAX_BUFFER_SIZE;                          //内存中开辟最大缓存块50 files

u_int64_t ALL_BUFFER_SIZE;                          //所有缓存块大小之和
F_HASH f_hash_arr;                           //hash table
F_BUF cur_buf = NULL;                               //while return back, this is current file buffer
F_BUF NO_BUF_TEMP = NULL;                           //当不可以缓存时，此为临时缓存链表头,最后根据此释放缓存

/**
 * @brief find out whether f_name exists
 * @param file's name
 * @return -1 	NOT EXIST
 * 			0 	EXIST BUT SIZE IS 0
 * 		   >0	SIZE
 */
long
get_file_size(const char *f_name)
{
    if (f_name == NULL)
        return -1;

    /* First! We'll check in hash table */
    /* check whether file in hash table. if in, return size and set cur_buf, else return -1 */
    long temp_size = check_file_in_hash_table(f_name);
    if (temp_size >= 0)
        return temp_size;
    //else //file not in buffer

    char *full_name = concat_path_name(FILEPATH, f_name);

    struct stat st;
    int ret = stat(full_name, &st);
    free(full_name);

    /* 打开文件失败或查找的并非普通文件 */
    if (ret == -1 || !S_ISREG(st.st_mode))
        return -1;
    else
    {
        /* 开线程异步读取文件 */
        pthread_t mythread;
        ret = pthread_create(&mythread, NULL, read_next_buf, (void*) f_name);
        if (ret != 0)
        {
            fprintf(stderr, "Create thread error\n");
            exit(-1);
        }
        ret = pthread_detach(mythread);
        if (ret != 0)
        {
            fprintf(stderr, "Set detached thread error\n");
            exit(-1);
        }
        return st.st_size;
    }
}

/**
 * @param flag whether send action ends; when it ends, we can release memory and close FILE*
 * @return size of buffer; when 0 returns, reading file down
 */
int
f_read(char **buf, int flag)
{
    /* 释放内存 */
    if (flag == 1)
    {
        if (NO_BUF_TEMP != NULL)
        {
            free_fbuf(NO_BUF_TEMP);
            NO_BUF_TEMP = NULL;
        }
        cur_buf = NULL;
        return 0;
    }
    /* 还没有读到内存中,休眠20ms */
    while (cur_buf == NULL) mysleep();
    if (cur_buf->block_buf == NULL)
        return 0;

    *buf = cur_buf->block_buf;
    while (cur_buf->next == NULL) mysleep();
    cur_buf = cur_buf->next;

    return strlen(*buf);
}

/**
 * 将文件异步保存到内存中,此为脱离线程
 * 同时查看文件是否可以缓存
 */
void *
read_next_buf(void *msg)
{
    char *f_name = strdup((char *) msg);
    char *full_name = concat_path_name(FILEPATH, f_name);

    int flag = 0;
    F_FILE ff = NULL;
    struct stat st;
    stat(full_name, &st);
    
    ff = checkIfCache(st.st_size, 1, f_name);
    if (ff != NULL)
        flag = 1;       // can be cached
    
    FILE *f_fp = fopen(full_name, "r");
    if (f_fp == NULL)
    {
        free(full_name);
        fprintf(stderr, "open file error");
        exit(-1);
    }

    F_BUF prev = NULL, ne;
    int i;
    for (;;)
    {
        ne = new(struct f_buf);
        ne->block_buf = (char*) malloc(BLOCK_SIZE + 1);
        ne->next = NULL;

        i = atom_read(f_fp, ne->block_buf, BLOCK_SIZE);
        ne->block_buf[i] = '\0';
        if (i <= 0)
            break;

        if (flag == 1 && ff->fl_buf == NULL) //can be buffered
        {
            cur_buf = prev = ne;
            ff->fl_buf = prev;
        }
        else if (flag == 0 && NO_BUF_TEMP == NULL) //can not be buffered, we'll make a temp buffer and at last release this buffers
        {
            cur_buf = prev = ne;
            NO_BUF_TEMP = prev;
        }
        else
        {
            prev->next = ne;
            prev = ne;
        }
    }

    /* 添加结束标志 */
    ne = new(struct f_buf);
    ne->block_buf = NULL;
    ne->next = NULL;
    prev->next = ne;

    free(full_name);
    free(f_name);
    fclose(f_fp);

    pthread_exit(NULL);
}

/*
 * 检查文件是否可以缓存
 * @param size--file's size
 * @param count--times of file being access
 * @param f_name--file's name
 * @return 如果文件可以缓存，则返回插入在树中的文本节点
 */
F_FILE
checkIfCache (long size, int count, char *f_name)
{
    // first check whether file exists
    F_FILE ff = NULL;
    char *full_name = concat_path_name(FILEPATH, f_name);
    if (access(full_name, F_OK) != 0)
        goto quit;
    
    int cur_pos;
    if (size <= BUFFER_FILE_SIZE) //can be buffered
    {
        /* file position in hash table,calculate by hash function */
        cur_pos = FPHash(f_name) & (HASH_TABLE_SIZE - 1); //hash table's position,be set by hash function
        if (size + ALL_BUFFER_SIZE <= MAX_BUFFER_SIZE) //没有超过最大缓存，可以直接插入
        {
            ff = Insert(f_name, size, count, f_hash_arr + cur_pos); //insert file info to binary tree of hash table
            ALL_BUFFER_SIZE += size;

            f_hash_arr[cur_pos].ha_size += size;
            f_hash_arr[cur_pos].ha_file_count++;
        }
        else //超过最大缓存，进行替换,同时需要修改总文件大小
        {
            int cur_pos_temp;           //简要删除节点的哈希表位置
            u_int64_t delete_size;
            /* 循环删除文件节点，直到可以进行缓存 */
            do
            {
                cur_pos_temp = cur_pos;
                /* 线性探测非空进行替换 */
                while (f_hash_arr[cur_pos_temp].ha_file == NULL)
                {
                    if (++cur_pos_temp >= HASH_TABLE_SIZE)
                        cur_pos_temp = 0;
                }
                //show_hash_table();
                delete_size = Find_Delete_LeastBuf_Proxy(f_hash_arr + cur_pos_temp, -1);
                f_hash_arr[cur_pos_temp].ha_size -= delete_size;
                f_hash_arr[cur_pos_temp].ha_file_count --;
                ALL_BUFFER_SIZE -= delete_size;
            }
            while (size + ALL_BUFFER_SIZE > MAX_BUFFER_SIZE);

            ff = Insert(f_name, size, count, f_hash_arr + cur_pos);
            ALL_BUFFER_SIZE += size;

            f_hash_arr[cur_pos].ha_size += size;
            f_hash_arr[cur_pos].ha_file_count++;
        }
    }
       
quit:
    free(full_name);
    return ff;
}

/**
 * read from fp to buf in atom
 */
size_t
atom_read(FILE *fp, char *buf, size_t count)
{
    size_t need = count, got;
    while ((got = fread(buf, sizeof (char), need, fp)) > 0 && (need -= got) > 0)
        buf += got;

    return (got < 0 ? got : (count - need));
}

/**
 * hash function
 */
unsigned int
FPHash(const char * str)
{
    unsigned int hash = 4321;

    while (*str)
        hash += (hash << 5) + (*str++);

    return (hash & 0x7FFFFFFF);
}

/**
 * 睡眠20ms
 */
void
mysleep()
{
    struct timeval *timeout = new(struct timeval);
    timeout->tv_sec = 0;
    timeout->tv_usec = 20 * 1000; //20ms
    select(0, NULL, NULL, NULL, timeout);
    free(timeout);
}

/*
 * 在哈希表中查看是否存在文件
 * 同时设置了读取数据指针
 * @return -1 在缓存中没有找到对应文件
 *         >0 找到对应文件，返回文件大小
 */
long
check_file_in_hash_table(const char *f_name)
{
    u_int32_t pos = FPHash(f_name) & (HASH_TABLE_SIZE - 1);

    if (f_hash_arr[pos].ha_file == NULL)
        return -1;
    else
    {
        F_FILE T = Find(f_name, f_hash_arr[pos].ha_file);
        if (!T)
            return -1;
        else
        {
            cur_buf = T->fl_buf;
            T->fl_count++;
            T->last_access = time(NULL);
            return T->fl_size;
        }
    }
}

/*
 * 检测文件节点是否失效，超过BUFFER_INVALID_TIME则将其从内存中移除
 */
void *
check_buffer_file_node_invalid(void *msg)
{
    int i;
    time_t now = time(NULL);
    for (i = 0; i < HASH_TABLE_SIZE; i++)
    {
        file_node_invalid(f_hash_arr[i].ha_file, now);
    }

    pthread_exit(NULL);
}

void 
show_hash_table ()
{
    int i;
    for (i = 0; i < HASH_TABLE_SIZE; i++)
    {
        printf("%d: \n", i);
        show_innerfix(f_hash_arr[i].ha_file);
        printf("\n");
    }
}